package dai.core.base.loader

import dai.core.base.*
import org.joml.Vector4f
import org.lwjgl.assimp.*
import org.lwjgl.assimp.Assimp.*
import java.lang.RuntimeException

class StaticMeshLoader {

    companion object {
        fun load(
            resourcePath: String,
            textureDir: String,
            flags: Int =
                        aiProcess_GenSmoothNormals or
                        aiProcess_JoinIdenticalVertices or
                        aiProcess_Triangulate or
                        aiProcess_FixInfacingNormals or
                        aiProcess_PreTransformVertices
        ): List<Mesh> {
            val aiScene = aiImportFile(resourcePath,flags) ?: throw RuntimeException("load model: ${resourcePath} failed!")

            // 材质
            val numMaterials = aiScene.mNumMaterials()
            val aiMaterials = aiScene.mMaterials()
            val materials = ArrayList<Material>()
            for (i in 0 until numMaterials){
                val tmpMaterial = AIMaterial.create(aiMaterials!![i])
                processMaterial(tmpMaterial,materials,textureDir)
            }

            // mesh
            val numMesh = aiScene.mNumMeshes()
            MyLog.info("numMesh:${numMesh}")
            val aiMeshes = aiScene.mMeshes()
            val meshes = ArrayList<Mesh>(numMesh)
            for (i in 0 until numMesh){
                val tmpMesh = AIMesh.create(aiMeshes!![i])
                val mesh = processMesh(tmpMesh,materials)
                meshes.add(mesh)
            }
            return meshes
        }

        private fun processMesh(aiMesh: AIMesh, materials: ArrayList<Material>): Mesh {
            val vertices = ArrayList<Float>()
            val texCoords = ArrayList<Float>()
            val normals = ArrayList<Float>()
            val indices = ArrayList<Int>()

            processVertices(aiMesh, vertices)
            processNormals(aiMesh, normals)
            processTextCoords(aiMesh, texCoords)
            processIndices(aiMesh, indices)

            // Texture coordinates may not have been populated. We need at least the empty slots
            if (texCoords.size == 0){
                val numElements = (vertices.size / 3) * 2
                for (i in 0 until numElements){
                    texCoords.add(0f)
                }
            }

            var material:Material
            val materialIdx = aiMesh.mMaterialIndex()
            if (materialIdx >= 0 && materialIdx < materials.size){
                material = materials[materialIdx]
            }else{
                material = Material()
            }

            val mesh = Mesh(
                positions = Utils.arrayListToArray(vertices),
                texCoords = Utils.arrayListToArray(texCoords),
                normals = Utils.arrayListToArray(normals),
                indices = Utils.arrayListToArray(indices),
                material = material
            )
            return mesh
        }

        private fun processIndices(aiMesh: AIMesh, indices: ArrayList<Int>) {
            val numFaces = aiMesh.mNumFaces()
            val aiFaces = aiMesh.mFaces()
            for (i in 0 until numFaces){
                val face = aiFaces.get(i)
                val buf = face.mIndices()
                while (buf.hasRemaining()){
                    indices.add(buf.get())
                }
            }
        }

        private fun processTextCoords(aiMesh: AIMesh, ret: ArrayList<Float>) {
            val aiTexCoords = aiMesh.mTextureCoords(0)
            val numTexCoords = aiTexCoords?.remaining()?:0
            for (i in 0 until numTexCoords){
                val texCoord = aiTexCoords!!.get()
                ret.add(texCoord.x())
                ret.add(1f - texCoord.y())
            }
        }

        private fun processNormals(aiMesh: AIMesh, normals: ArrayList<Float>) {
            val aiNormals = aiMesh.mNormals()
            aiNormals?.let {
                while (it.hasRemaining()){
                    val normal = it.get()
                    normals.add(normal.x())
                    normals.add(normal.y())
                    normals.add(normal.z())
                }
            }
        }

        private fun processVertices(aiMesh: AIMesh, vertices: ArrayList<Float>) {
            val aiVertices = aiMesh.mVertices()
            while (aiVertices.hasRemaining()){
                val vertex = aiVertices.get()
                vertices.add(vertex.x())
                vertices.add(vertex.y())
                vertices.add(vertex.z())
            }
        }

        private fun processMaterial(aiMaterial: AIMaterial, materials: ArrayList<Material>, textureDir: String) {

            // diffuse 纹理
            val path = AIString.calloc()
            val flags:IntArray? = null
            aiGetMaterialTexture(aiMaterial, aiTextureType_DIFFUSE,0,path,null,null,null,null,null,flags)
            val textPath = path.dataString()
            var texture:Texture? = null
            if (!textPath.isNullOrBlank()){
                var textureFile = ""
                if (!textureDir.isNullOrBlank()){
                    textureFile += "$textureDir/"
                }
                textureFile  += textPath
                textureFile = textureFile.replace("//","/")
                texture = TextureCache.getTexture(textureFile)
            }

            // ambient diffuse specular
            val color = AIColor4D.create()
            val ambient = Material.DEFAULT_COLOR.clone() as Vector4f
            var result = aiGetMaterialColor(aiMaterial, AI_MATKEY_COLOR_AMBIENT,aiTextureType_NONE,0,color)
            if (result == 0){
                ambient.set(color.r(),color.g(),color.b(),color.a())
            }

            val diffuse = Material.DEFAULT_COLOR.clone() as Vector4f
            result = aiGetMaterialColor(aiMaterial, AI_MATKEY_COLOR_DIFFUSE, aiTextureType_NONE,0,color)
            if (result == 0){
                diffuse.set(color.r(),color.g(),color.b(),color.a())
            }

            val specular = Material.DEFAULT_COLOR.clone() as Vector4f
            result = aiGetMaterialColor(aiMaterial, AI_MATKEY_COLOR_SPECULAR, aiTextureType_NONE,0,color)
            if (result == 0){
                specular.set(color.r(),color.g(),color.b(),color.a())
            }

            val material = Material(ambient,diffuse,specular,1f,0f,texture)
            materials.add(material)
        }
    }
}

